package weeny.util.io;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * To clean up the the &ABCD; or &#1234; in the html content
 * 
 * Call {@link #toUTF(String)} to get a clean version of html text
 * 
 * @author weeny
 *
 */
public class HtmlCoder {
	private static Map<String, String> numberCodes = new HashMap();
	private static Map<String, String> letterCodes = new HashMap();
	private static Pattern targetPattern = null;
	
	/**
	 * Give you a clean version of html text (cleaning up all all, hopefully,
	 * &ABCD, &#1234 codes). 
	 */
	public static String toUTF(String text){
		if (text == null) {
			return null;
		}
		if (targetPattern == null) {
			int minCnt = 1000;
			int maxCnt = -1;
			for(String key : letterCodes.keySet()){
				int tmpLength = key.length()-2;
				maxCnt = Math.max(tmpLength, maxCnt);
				minCnt = Math.min(tmpLength, minCnt);
			}
			String friendPattern = "(?:&[a-zA-Z]{"+minCnt+","+maxCnt+"};)";
			
			for(String key : numberCodes.keySet()){
				int tmpLength = key.length()-3;
				maxCnt = Math.max(tmpLength, maxCnt);
				minCnt = Math.min(tmpLength, minCnt);
			}
			String numberPattern = "(?:&#[0-9]{"+minCnt+","+maxCnt+"};)";

			targetPattern = Pattern.compile(friendPattern +"|"+numberPattern);
		}
		Matcher matcher = targetPattern.matcher(text);
		boolean found = matcher.find();
		if (!found) {
			return text;
		}
		Set<String> fails = new HashSet<String>();
		StringBuffer sb = new StringBuffer();
		do {
			String key = matcher.group(0);
			if (letterCodes.containsKey(key)) {
				matcher.appendReplacement(sb, letterCodes.get(key));
			}
			else if (numberCodes.containsKey(key)) {
				matcher.appendReplacement(sb, numberCodes.get(key));
			}
			else{
				fails.add(key);
			}
			found = matcher.find();
		} while (found);
		matcher.appendTail(sb);
		if (!fails.isEmpty()) {
			System.err.println("Still cannot find: "+fails);	
		}
		return sb.toString();
	}
	
	static{
		letterCodes.put("&quot;","\"");
		letterCodes.put("&amp;","&");
		letterCodes.put("&tilde;","~");
		letterCodes.put("&sbquo;","‚");
		letterCodes.put("&dbquo;","„");
		letterCodes.put("&dagger;","†");
		letterCodes.put("&Dagger;","‡");
		letterCodes.put("&lsaquo;","‹");
		letterCodes.put("&lsquo;","‘");
		letterCodes.put("&rsquo;","’");
		letterCodes.put("&ldquo;","“");
		letterCodes.put("&rdquo;","”");
		letterCodes.put("&ndash;","–");
		letterCodes.put("&mdash;","—");
		letterCodes.put("&trade;","™");
		letterCodes.put("&rsaquo;","›");
		letterCodes.put("&nbsp;"," ");
		letterCodes.put("&iexcl;","¡");
		letterCodes.put("&brvbar;","¦");
		letterCodes.put("&copy;","©");
		letterCodes.put("&ordf;","ª");
		letterCodes.put("&laquo;","«");
		letterCodes.put("&not;","¬");
		letterCodes.put("&shy;","­");
		letterCodes.put("&reg;","®");
		letterCodes.put("&deg;","°");
		letterCodes.put("&sup2;","²");
		letterCodes.put("&sup3;","³");
		letterCodes.put("&micro;","µ");
		letterCodes.put("&para;","¶");
		letterCodes.put("&middot;","·");
		letterCodes.put("&sup1;","¹");
		letterCodes.put("&ordm;","º");
		letterCodes.put("&raquo;","»");
		letterCodes.put("&iquest;","¿");
		letterCodes.put("&sect;","§");
		letterCodes.put("&iquest;","¨");
		letterCodes.put("&oline;","‾");
		letterCodes.put("&eth;","ð");
		letterCodes.put("&ntilde;","ñ");
		letterCodes.put("&ograve;","ò");
		letterCodes.put("&oacute;","ó");
		letterCodes.put("&ocirc;","ô");
		letterCodes.put("&otilde;","õ");
		letterCodes.put("&ouml;","ö");
		letterCodes.put("&divide;","÷");
		letterCodes.put("&oslash;","ø");
		letterCodes.put("&ugrave;","ù");
		letterCodes.put("&uacute;","ú");
		letterCodes.put("&ucirc;","û");
		letterCodes.put("&uuml;","ü");
		letterCodes.put("&yacute;","ý");
		letterCodes.put("&thorn;","þ");
		letterCodes.put("&yuml;","ÿ");
		letterCodes.put("&agrave;","à");
		letterCodes.put("&aacute;","á");
		letterCodes.put("&acirc;","â");
		letterCodes.put("&atilde;","ã");
		letterCodes.put("&auml;","ä");
		letterCodes.put("&aring;","å");
		letterCodes.put("&aelig;","æ");
		letterCodes.put("&ccedil;","ç");
		letterCodes.put("&egrave;","è");
		letterCodes.put("&eacute;","é");
		letterCodes.put("&ecirc;","ê");
		letterCodes.put("&euml;","ë");
		letterCodes.put("&igrave;","ì");
		letterCodes.put("&iacute;","í");
		letterCodes.put("&icirc;","î");
		letterCodes.put("&iuml;","ï");
		letterCodes.put("&ETH;","Ð");
		letterCodes.put("&Ntilde;","Ñ");
		letterCodes.put("&Ograve;","Ò");
		letterCodes.put("&Oacute;","Ó");
		letterCodes.put("&Ocirc;","Ô");
		letterCodes.put("&Otilde;","Õ");
		letterCodes.put("&Ouml;","Ö");
		letterCodes.put("&times;","×");
		letterCodes.put("&Oslash;","Ø");
		letterCodes.put("&Ugrave;","Ù");
		letterCodes.put("&Uacute;","Ú");
		letterCodes.put("&Ucirc;","Û");
		letterCodes.put("&Uuml;","Ü");
		letterCodes.put("&Yacute;","Ý");
		letterCodes.put("&THORN;","Þ");
		letterCodes.put("&szlig;","ß");
		letterCodes.put("&Agrave;","À");
		letterCodes.put("&Aacute;","Á");
		letterCodes.put("&Acirc;","Â");
		letterCodes.put("&Atilde;","Ã");
		letterCodes.put("&Auml;","Ä");
		letterCodes.put("&Aring;","Å");
		letterCodes.put("&AElig;","Æ");
		letterCodes.put("&Ccedil;","Ç");
		letterCodes.put("&Egrave;","È");
		letterCodes.put("&Eacute;","É");
		letterCodes.put("&Ecirc;","Ê");
		letterCodes.put("&Euml;","Ë");
		letterCodes.put("&Igrave;","Ì");
		letterCodes.put("&Iacute;","Í");
		letterCodes.put("&Icirc;","Î");
		letterCodes.put("&Iuml;","Ï");
		letterCodes.put("&deg;","°");
		letterCodes.put("&plusmn;","±");
		letterCodes.put("&sup2;","²");
		letterCodes.put("&sup3;","³");
		letterCodes.put("&acute;","´");
		letterCodes.put("&micro;","µ");
		letterCodes.put("&para;","¶");
		letterCodes.put("&middot;","·");
		letterCodes.put("&cedil;","¸");
		letterCodes.put("&sup1;","¹");
		letterCodes.put("&ordm;","º");
		letterCodes.put("&raquo;","»");
		letterCodes.put("&frac14;","¼");
		letterCodes.put("&frac12;","½");
		letterCodes.put("&frac34;","¾");
		letterCodes.put("&iquest;","¿");
		
		numberCodes.put("&#33;","!");
		numberCodes.put("&#34;","\"");
		numberCodes.put("&#35;","#");
		numberCodes.put("&#38;","&");
		numberCodes.put("&#39;","'");
		numberCodes.put("&#40;","(");
		numberCodes.put("&#41;",")");
		numberCodes.put("&#42;","*");
		numberCodes.put("&#44;",",");
		numberCodes.put("&#45;","-");
		numberCodes.put("&#46;",".");
		numberCodes.put("&#47;","/");
		numberCodes.put("&#58;",":");
		numberCodes.put("&#59;",";");
		numberCodes.put("&#63;","?");
		numberCodes.put("&#64;","@");
		numberCodes.put("&#91;","[");
		numberCodes.put("&#92;","\\");
		numberCodes.put("&#93;","]");
		numberCodes.put("&#94;","^");
		numberCodes.put("&#95;","_");
		numberCodes.put("&#123;","{");
		numberCodes.put("&#124;","|");
		numberCodes.put("&#125;","}");
		numberCodes.put("&#126;","~");
		numberCodes.put("&#130;","‚");
		numberCodes.put("&#132;","„");
		numberCodes.put("&#133;","…");
		numberCodes.put("&#134;","†");
		numberCodes.put("&#135;","‡");
		numberCodes.put("&#139;","‹");
		numberCodes.put("&#145;","‘");
		numberCodes.put("&#146;","’");
		numberCodes.put("&#147;","“");
		numberCodes.put("&#148;","”");
		numberCodes.put("&#149;","•");
		numberCodes.put("&#150;","–");
		numberCodes.put("&#151;","—");
		numberCodes.put("&#153;","™");
		numberCodes.put("&#155;","›");
		numberCodes.put("&#160;","");
		numberCodes.put("&#161;","¡");
		numberCodes.put("&#166;","¦");
		numberCodes.put("&#169;","©");
		numberCodes.put("&#170;","ª");
		numberCodes.put("&#171;","«");
		numberCodes.put("&#172;","¬");
		numberCodes.put("&#173;","­");
		numberCodes.put("&#174;","®");
		numberCodes.put("&#176;","°");
		numberCodes.put("&#178;","²");
		numberCodes.put("&#179;","³");
		numberCodes.put("&#181;","µ");
		numberCodes.put("&#182;","¶");
		numberCodes.put("&#183;","·");
		numberCodes.put("&#185;","¹");
		numberCodes.put("&#186;","º");
		numberCodes.put("&#187;","»");
		numberCodes.put("&#191;","¿");
		numberCodes.put("&#8453;","℅");
		numberCodes.put("&#8319;","ⁿ");
		numberCodes.put("&#167;","§");
		numberCodes.put("&#191;","¨");
		numberCodes.put("&#8213;","―");
		numberCodes.put("&#8227;","‣");
		numberCodes.put("&#8254;","‾");
		numberCodes.put("&#8252;","‼");
		numberCodes.put("&#8470;","№");
		numberCodes.put("&#8211;","–");
		numberCodes.put("&#8212;","—");
		numberCodes.put("&#8216;","‘");
		numberCodes.put("&#8217;","’");
		numberCodes.put("&#8218;","‚");
		numberCodes.put("&#8220;","“");
		numberCodes.put("&#8221;","”");
		numberCodes.put("&#8222;","„");
		numberCodes.put("&#8224;","†");
		numberCodes.put("&#8225;","‡");
		numberCodes.put("&#8226;","•");
		numberCodes.put("&#8230;","…");
		numberCodes.put("&#8240;","‰");
		numberCodes.put("&#8364;","€");
		numberCodes.put("&#8482;","™");
		numberCodes.put("&#338;","Œ");
		numberCodes.put("&#339;","œ");
		numberCodes.put("&#352;","Š");
		numberCodes.put("&#353;","š");
		numberCodes.put("&#376;","Ÿ");
		numberCodes.put("&#402;","ƒ");
		numberCodes.put("&#240;","ð");
		numberCodes.put("&#241;","ñ");
		numberCodes.put("&#242;","ò");
		numberCodes.put("&#243;","ó");
		numberCodes.put("&#244;","ô");
		numberCodes.put("&#245;","õ");
		numberCodes.put("&#246;","ö");
		numberCodes.put("&#247;","÷");
		numberCodes.put("&#248;","ø");
		numberCodes.put("&#249;","ù");
		numberCodes.put("&#250;","ú");
		numberCodes.put("&#251;","û");
		numberCodes.put("&#252;","ü");
		numberCodes.put("&#253;","ý");
		numberCodes.put("&#254;","þ");
		numberCodes.put("&#255;","ÿ");
		numberCodes.put("&#224;","à");
		numberCodes.put("&#225;","á");
		numberCodes.put("&#226;","â");
		numberCodes.put("&#227;","ã");
		numberCodes.put("&#228;","ä");
		numberCodes.put("&#229;","å");
		numberCodes.put("&#230;","æ");
		numberCodes.put("&#231;","ç");
		numberCodes.put("&#232;","è");
		numberCodes.put("&#233;","é");
		numberCodes.put("&#234;","ê");
		numberCodes.put("&#235;","ë");
		numberCodes.put("&#236;","ì");
		numberCodes.put("&#237;","í");
		numberCodes.put("&#238;","î");
		numberCodes.put("&#239;","ï");
		numberCodes.put("&#208;","Ð");
		numberCodes.put("&#209;","Ñ");
		numberCodes.put("&#210;","Ò");
		numberCodes.put("&#211;","Ó");
		numberCodes.put("&#212;","Ô");
		numberCodes.put("&#213;","Õ");
		numberCodes.put("&#214;","Ö");
		numberCodes.put("&#215;","×");
		numberCodes.put("&#216;","Ø");
		numberCodes.put("&#217;","Ù");
		numberCodes.put("&#218;","Ú");
		numberCodes.put("&#219;","Û");
		numberCodes.put("&#220;","Ü");
		numberCodes.put("&#221;","Ý");
		numberCodes.put("&#222;","Þ");
		numberCodes.put("&#223;","ß");
		numberCodes.put("&#192;","À");
		numberCodes.put("&#193;","Á");
		numberCodes.put("&#194;","Â");
		numberCodes.put("&#195;","Ã");
		numberCodes.put("&#196;","Ä");
		numberCodes.put("&#197;","Å");
		numberCodes.put("&#198;","Æ");
		numberCodes.put("&#199;","Ç");
		numberCodes.put("&#200;","È");
		numberCodes.put("&#201;","É");
		numberCodes.put("&#202;","Ê");
		numberCodes.put("&#203;","Ë");
		numberCodes.put("&#204;","Ì");
		numberCodes.put("&#205;","Í");
		numberCodes.put("&#206;","Î");
		numberCodes.put("&#207;","Ï");
		numberCodes.put("&#176;","°");
		numberCodes.put("&#177;","±");
		numberCodes.put("&#178;","²");
		numberCodes.put("&#179;","³");
		numberCodes.put("&#180;","´");
		numberCodes.put("&#181;","µ");
		numberCodes.put("&#182;","¶");
		numberCodes.put("&#183;","·");
		numberCodes.put("&#184;","¸");
		numberCodes.put("&#185;","¹");
		numberCodes.put("&#186;","º");
		numberCodes.put("&#187;","»");
		numberCodes.put("&#188;","¼");
		numberCodes.put("&#189;","½");
		numberCodes.put("&#190;","¾");
		numberCodes.put("&#191;","¿");
	}
	
	public static void main(String[] args) {
		System.out.println(toUTF("The Caucus: Obama&#8217;s Agenda Carries" +
				" a Few Bruises&nbsp;-&nbsp;Feature"));
	}
}
